1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   */
19  package org.codehaus.groovy.vmplugin.v5;
20  
21  import org.codehaus.groovy.GroovyBugError;
22  import org.codehaus.groovy.ast.AnnotatedNode;
23  import org.codehaus.groovy.ast.AnnotationNode;
24  import org.codehaus.groovy.ast.ClassHelper;
25  import org.codehaus.groovy.ast.ClassNode;
26  import org.codehaus.groovy.ast.CompileUnit;
27  import org.codehaus.groovy.ast.FieldNode;
28  import org.codehaus.groovy.ast.GenericsType;
29  import org.codehaus.groovy.ast.MethodNode;
30  import org.codehaus.groovy.ast.PackageNode;
31  import org.codehaus.groovy.ast.Parameter;
32  import org.codehaus.groovy.ast.expr.ClassExpression;
33  import org.codehaus.groovy.ast.expr.ConstantExpression;
34  import org.codehaus.groovy.ast.expr.Expression;
35  import org.codehaus.groovy.ast.expr.ListExpression;
36  import org.codehaus.groovy.ast.expr.PropertyExpression;
37  import org.codehaus.groovy.ast.stmt.ReturnStatement;
38  import org.codehaus.groovy.vmplugin.VMPlugin;
39  
40  import java.lang.annotation.Annotation;
41  import java.lang.annotation.ElementType;
42  import java.lang.annotation.Retention;
43  import java.lang.annotation.RetentionPolicy;
44  import java.lang.annotation.Target;
45  import java.lang.reflect.Array;
46  import java.lang.reflect.Constructor;
47  import java.lang.reflect.Field;
48  import java.lang.reflect.GenericArrayType;
49  import java.lang.reflect.InvocationTargetException;
50  import java.lang.reflect.Method;
51  import java.lang.reflect.ParameterizedType;
52  import java.lang.reflect.Type;
53  import java.lang.reflect.TypeVariable;
54  import java.lang.reflect.WildcardType;
55  import java.util.List;
56  
57  /**
58   * java 5 based functions
59   *
60   * @author Jochen Theodorou
61   */
62  public class Java5 implements VMPlugin {
63      private static Class[] EMPTY_CLASS_ARRAY = new Class[0];
64      private static final Class[] PLUGIN_DGM = {PluginDefaultGroovyMethods.class};
65  
66      public void setAdditionalClassInformation(ClassNode cn) {
67          setGenericsTypes(cn);
68      }
69  
70      private void setGenericsTypes(ClassNode cn) {
71          TypeVariable[] tvs = cn.getTypeClass().getTypeParameters();
72          GenericsType[] gts = configureTypeVariable(tvs);
73          cn.setGenericsTypes(gts);
74      }
75  
76      private GenericsType[] configureTypeVariable(TypeVariable[] tvs) {
77          if (tvs.length == 0) return null;
78          GenericsType[] gts = new GenericsType[tvs.length];
79          for (int i = 0; i < tvs.length; i++) {
80              gts[i] = configureTypeVariableDefinition(tvs[i]);
81          }
82          return gts;
83      }
84  
85      private GenericsType configureTypeVariableDefinition(TypeVariable tv) {
86          ClassNode base = configureTypeVariableReference(tv);
87          ClassNode redirect = base.redirect();
88          base.setRedirect(null);
89          Type[] tBounds = tv.getBounds();
90          GenericsType gt;
91          if (tBounds.length == 0) {
92              gt = new GenericsType(base);
93          } else {
94              ClassNode[] cBounds = configureTypes(tBounds);
95              gt = new GenericsType(base, cBounds, null);
96              gt.setName(base.getName());
97              gt.setPlaceholder(true);
98          }
99          base.setRedirect(redirect);
100         return gt;
101     }
102 
103     private ClassNode[] configureTypes(Type[] types) {
104         if (types.length == 0) return null;
105         ClassNode[] nodes = new ClassNode[types.length];
106         for (int i = 0; i < types.length; i++) {
107             nodes[i] = configureType(types[i]);
108         }
109         return nodes;
110     }
111 
112     private ClassNode configureType(Type type) {
113         if (type instanceof WildcardType) {
114             return configureWildcardType((WildcardType) type);
115         } else if (type instanceof ParameterizedType) {
116             return configureParameterizedType((ParameterizedType) type);
117         } else if (type instanceof GenericArrayType) {
118             return configureGenericArray((GenericArrayType) type);
119         } else if (type instanceof TypeVariable) {
120             return configureTypeVariableReference((TypeVariable) type);
121         } else if (type instanceof Class) {
122             return configureClass((Class) type);
123         } else if (type==null) {
124             throw new GroovyBugError("Type is null. Most probably you let a transform reuse existing ClassNodes with generics information, that is now used in a wrong context.");
125         } else {
126             throw new GroovyBugError("unknown type: " + type + " := " + type.getClass());
127         }
128     }
129 
130     private ClassNode configureClass(Class c) {
131         if (c.isPrimitive()) {
132             return ClassHelper.make(c);
133         } else {
134             return ClassHelper.makeWithoutCaching(c, false);
135         }
136     }
137 
138     private ClassNode configureGenericArray(GenericArrayType genericArrayType) {
139         Type component = genericArrayType.getGenericComponentType();
140         ClassNode node = configureType(component);
141         return node.makeArray();
142     }
143 
144     private ClassNode configureWildcardType(WildcardType wildcardType) {
145         ClassNode base = ClassHelper.makeWithoutCaching("?");
146         base.setRedirect(ClassHelper.OBJECT_TYPE);
147         //TODO: more than one lower bound for wildcards?
148         ClassNode[] lowers = configureTypes(wildcardType.getLowerBounds());
149         ClassNode lower = null;
150         // TODO: is it safe to remove this? What was the original intention?
151         if (lowers != null) lower = lowers[0];
152 
153         ClassNode[] upper = configureTypes(wildcardType.getUpperBounds());
154         GenericsType t = new GenericsType(base, upper, lower);
155         t.setWildcard(true);
156 
157         ClassNode ref = ClassHelper.makeWithoutCaching(Object.class, false);
158         ref.setGenericsTypes(new GenericsType[]{t});
159 
160         return ref;
161     }
162 
163     private ClassNode configureParameterizedType(ParameterizedType parameterizedType) {
164         ClassNode base = configureType(parameterizedType.getRawType());
165         GenericsType[] gts = configureTypeArguments(parameterizedType.getActualTypeArguments());
166         base.setGenericsTypes(gts);
167         return base;
168     }
169 
170     private ClassNode configureTypeVariableReference(TypeVariable tv) {
171         ClassNode cn = ClassHelper.makeWithoutCaching(tv.getName());
172         cn.setGenericsPlaceHolder(true);
173         ClassNode cn2 = ClassHelper.makeWithoutCaching(tv.getName());
174         cn2.setGenericsPlaceHolder(true);
175         GenericsType[] gts = new GenericsType[]{new GenericsType(cn2)};
176         cn.setGenericsTypes(gts);
177         cn.setRedirect(ClassHelper.OBJECT_TYPE);
178         return cn;
179     }
180 
181     private GenericsType[] configureTypeArguments(Type[] ta) {
182         if (ta.length == 0) return null;
183         GenericsType[] gts = new GenericsType[ta.length];
184         for (int i = 0; i < ta.length; i++) {
185             ClassNode t = configureType(ta[i]);
186             if (ta[i] instanceof WildcardType) {
187                 GenericsType[] gen = t.getGenericsTypes();
188                 gts[i] = gen[0];
189             } else {
190                 gts[i] = new GenericsType(t);
191             }
192         }
193         return gts;
194     }
195 
196     public Class[] getPluginDefaultGroovyMethods() {
197         return PLUGIN_DGM;
198     }
199 
200     public Class[] getPluginStaticGroovyMethods() {
201         return EMPTY_CLASS_ARRAY;
202     }
203 
204     private void setAnnotationMetaData(Annotation[] annotations, AnnotatedNode an) {
205         for (Annotation annotation : annotations) {
206             AnnotationNode node = new AnnotationNode(ClassHelper.make(annotation.annotationType()));
207             configureAnnotation(node, annotation);
208             an.addAnnotation(node);
209         }
210     }
211 
212     private void configureAnnotationFromDefinition(AnnotationNode definition, AnnotationNode root) {
213         ClassNode type = definition.getClassNode();
214         if (!type.isResolved()) return;
215         Class clazz = type.getTypeClass();
216         if (clazz == Retention.class) {
217             Expression exp = definition.getMember("value");
218             if (!(exp instanceof PropertyExpression)) return;
219             PropertyExpression pe = (PropertyExpression) exp;
220             String name = pe.getPropertyAsString();
221             RetentionPolicy policy = RetentionPolicy.valueOf(name);
222             setRetentionPolicy(policy, root);
223         } else if (clazz == Target.class) {
224             Expression exp = definition.getMember("value");
225             if (!(exp instanceof ListExpression)) return;
226             ListExpression le = (ListExpression) exp;
227             int bitmap = 0;
228             for (Expression e : le.getExpressions()) {
229                 if (!(e instanceof PropertyExpression)) return;
230                 PropertyExpression element = (PropertyExpression) e;
231                 String name = element.getPropertyAsString();
232                 ElementType value = ElementType.valueOf(name);
233                 bitmap |= getElementCode(value);
234             }
235             root.setAllowedTargets(bitmap);
236         }
237     }
238 
239     public void configureAnnotation(AnnotationNode node) {
240         ClassNode type = node.getClassNode();
241         List<AnnotationNode> annotations = type.getAnnotations();
242         for (AnnotationNode an : annotations) {
243             configureAnnotationFromDefinition(an, node);
244         }
245         configureAnnotationFromDefinition(node, node);
246     }
247 
248     private void configureAnnotation(AnnotationNode node, Annotation annotation) {
249         Class type = annotation.annotationType();
250         if (type == Retention.class) {
251             Retention r = (Retention) annotation;
252             RetentionPolicy value = r.value();
253             setRetentionPolicy(value, node);
254             node.setMember("value", new PropertyExpression(
255                     new ClassExpression(ClassHelper.makeWithoutCaching(RetentionPolicy.class, false)),
256                     value.toString()));
257         } else if (type == Target.class) {
258             Target t = (Target) annotation;
259             ElementType[] elements = t.value();
260             ListExpression elementExprs = new ListExpression();
261             for (ElementType element : elements) {
262                 elementExprs.addExpression(new PropertyExpression(
263                         new ClassExpression(ClassHelper.ELEMENT_TYPE_TYPE), element.name()));
264             }
265             node.setMember("value", elementExprs);
266         } else {
267             Method[] declaredMethods;
268             try {
269                 declaredMethods = type.getDeclaredMethods();
270             } catch (SecurityException se) {
271                 declaredMethods = new Method[0];
272             }
273             for (Method declaredMethod : declaredMethods) {
274                 try {
275                     Object value = declaredMethod.invoke(annotation);
276                     Expression valueExpression = annotationValueToExpression(value);
277                     if (valueExpression == null)
278                         continue;
279                     node.setMember(declaredMethod.getName(), valueExpression);
280                 } catch (IllegalAccessException e) {
281                 } catch (InvocationTargetException e) {
282                 }
283             }
284         }
285     }
286 
287     private Expression annotationValueToExpression (Object value) {
288         if (value == null || value instanceof String || value instanceof Number || value instanceof Character || value instanceof Boolean)
289             return new ConstantExpression(value);
290 
291         if (value instanceof Class)
292             return new ClassExpression(ClassHelper.makeWithoutCaching((Class)value));
293 
294         if (value.getClass().isArray()) {
295             ListExpression elementExprs = new ListExpression();
296             int len = Array.getLength(value);
297             for (int i = 0; i != len; ++i)
298                 elementExprs.addExpression(annotationValueToExpression(Array.get(value, i)));
299             return elementExprs;
300         }
301 
302         return null;
303     }
304 
305     private void setRetentionPolicy(RetentionPolicy value, AnnotationNode node) {
306         switch (value) {
307             case RUNTIME:
308                 node.setRuntimeRetention(true);
309                 break;
310             case SOURCE:
311                 node.setSourceRetention(true);
312                 break;
313             case CLASS:
314                 node.setClassRetention(true);
315                 break;
316             default:
317                 throw new GroovyBugError("unsupported Retention " + value);
318         }
319     }
320 
321     private int getElementCode(ElementType value) {
322         switch (value) {
323             case TYPE:
324                 return AnnotationNode.TYPE_TARGET;
325             case CONSTRUCTOR:
326                 return AnnotationNode.CONSTRUCTOR_TARGET;
327             case METHOD:
328                 return AnnotationNode.METHOD_TARGET;
329             case FIELD:
330                 return AnnotationNode.FIELD_TARGET;
331             case PARAMETER:
332                 return AnnotationNode.PARAMETER_TARGET;
333             case LOCAL_VARIABLE:
334                 return AnnotationNode.LOCAL_VARIABLE_TARGET;
335             case ANNOTATION_TYPE:
336                 return AnnotationNode.ANNOTATION_TARGET;
337             case PACKAGE:
338                 return AnnotationNode.PACKAGE_TARGET;
339             default:
340                 throw new GroovyBugError("unsupported Target " + value);
341         }
342     }
343 
344     private void setMethodDefaultValue(MethodNode mn, Method m) {
345         Object defaultValue = m.getDefaultValue();
346         ConstantExpression cExp = ConstantExpression.NULL;
347         if (defaultValue!=null) cExp = new ConstantExpression(defaultValue);
348         mn.setCode(new ReturnStatement(cExp));
349         mn.setAnnotationDefault(true);
350     }
351 
352     public void configureClassNode(CompileUnit compileUnit, ClassNode classNode) {
353         try {
354             Class clazz = classNode.getTypeClass();
355             Field[] fields = clazz.getDeclaredFields();
356             for (Field f : fields) {
357                 ClassNode ret = makeClassNode(compileUnit, f.getGenericType(), f.getType());
358                 FieldNode fn = new FieldNode(f.getName(), f.getModifiers(), ret, classNode, null);
359                 setAnnotationMetaData(f.getAnnotations(), fn);
360                 classNode.addField(fn);
361             }
362             Method[] methods = clazz.getDeclaredMethods();
363             for (Method m : methods) {
364                 ClassNode ret = makeClassNode(compileUnit, m.getGenericReturnType(), m.getReturnType());
365                 Parameter[] params = makeParameters(compileUnit, m.getGenericParameterTypes(), m.getParameterTypes(), m.getParameterAnnotations());
366                 ClassNode[] exceptions = makeClassNodes(compileUnit, m.getGenericExceptionTypes(), m.getExceptionTypes());
367                 MethodNode mn = new MethodNode(m.getName(), m.getModifiers(), ret, params, exceptions, null);
368                 mn.setSynthetic(m.isSynthetic());
369                 setMethodDefaultValue(mn, m);
370                 setAnnotationMetaData(m.getAnnotations(), mn);
371                 mn.setGenericsTypes(configureTypeVariable(m.getTypeParameters()));
372                 classNode.addMethod(mn);
373             }
374             Constructor[] constructors = clazz.getDeclaredConstructors();
375             for (Constructor ctor : constructors) {
376                 Parameter[] params = makeParameters(compileUnit, ctor.getGenericParameterTypes(), ctor.getParameterTypes(), ctor.getParameterAnnotations());
377                 ClassNode[] exceptions = makeClassNodes(compileUnit, ctor.getGenericExceptionTypes(), ctor.getExceptionTypes());
378                 classNode.addConstructor(ctor.getModifiers(), params, exceptions, null);
379             }
380 
381             Class sc = clazz.getSuperclass();
382             if (sc != null) classNode.setUnresolvedSuperClass(makeClassNode(compileUnit, clazz.getGenericSuperclass(), sc));
383             makeInterfaceTypes(compileUnit, classNode, clazz);
384             setAnnotationMetaData(classNode.getTypeClass().getAnnotations(), classNode);
385 
386             PackageNode packageNode = classNode.getPackage();
387             if (packageNode != null) {
388                 setAnnotationMetaData(classNode.getTypeClass().getPackage().getAnnotations(), packageNode);
389             }
390         } catch (NoClassDefFoundError e) {
391             throw new NoClassDefFoundError("Unable to load class "+classNode.toString(false)+" due to missing dependency "+e.getMessage());
392         }
393     }
394 
395     private void makeInterfaceTypes(CompileUnit cu, ClassNode classNode, Class clazz) {
396         Type[] interfaceTypes = clazz.getGenericInterfaces();
397         if (interfaceTypes.length == 0) {
398             classNode.setInterfaces(ClassNode.EMPTY_ARRAY);
399         } else {
400             ClassNode[] ret = new ClassNode[interfaceTypes.length];
401             for (int i = 0; i < interfaceTypes.length; i++) {
402                 Type type = interfaceTypes[i];
403                 while (!(type instanceof Class)) {
404                     ParameterizedType pt = (ParameterizedType) type;
405                     Type t2 = pt.getRawType();
406                     if (t2==type) {
407                         throw new GroovyBugError("Cannot transform generic signature of "+clazz+
408                                 " with generic interface "+interfaceTypes[i]+" to a class.");
409                     }
410                     type = t2;
411                 }
412                 ret[i] = makeClassNode(cu, interfaceTypes[i], (Class) type);
413             }
414             classNode.setInterfaces(ret);
415         }
416     }
417 
418     private ClassNode[] makeClassNodes(CompileUnit cu, Type[] types, Class[] cls) {
419         ClassNode[] nodes = new ClassNode[types.length];
420         for (int i = 0; i < nodes.length; i++) {
421             nodes[i] = makeClassNode(cu, types[i], cls[i]);
422         }
423         return nodes;
424     }
425 
426     private ClassNode makeClassNode(CompileUnit cu, Type t, Class c) {
427         ClassNode back = null;
428         if (cu != null) back = cu.getClass(c.getName());
429         if (back == null) back = ClassHelper.make(c);
430         if (!(t instanceof Class)) {
431             ClassNode front = configureType(t);
432             front.setRedirect(back);
433             return front;
434         }
435         return back.getPlainNodeReference();
436     }
437 
438     private Parameter[] makeParameters(CompileUnit cu, Type[] types, Class[] cls, Annotation[][] parameterAnnotations) {
439         Parameter[] params = Parameter.EMPTY_ARRAY;
440         if (types.length > 0) {
441             params = new Parameter[types.length];
442             for (int i = 0; i < params.length; i++) {
443                 params[i] = makeParameter(cu, types[i], cls[i], parameterAnnotations[i], i);
444             }
445         }
446         return params;
447     }
448 
449     private Parameter makeParameter(CompileUnit cu, Type type, Class cl, Annotation[] annotations, int idx) {
450         ClassNode cn = makeClassNode(cu, type, cl);
451         Parameter parameter = new Parameter(cn, "param" + idx);
452         setAnnotationMetaData(annotations, parameter);
453         return parameter;
454     }
455 
456     public void invalidateCallSites() {}
457 
458     @Override
459     public Object getInvokeSpecialHandle(Method m, Object receiver){
460         throw new GroovyBugError("getInvokeSpecialHandle requires at least JDK 7 wot private access to Lookup");
461     }
462 
463     @Override
464     public int getVersion() {
465         return 5;
466     }
467 
468     @Override
469     public Object invokeHandle(Object handle, Object[] args) throws Throwable {
470         throw new GroovyBugError("invokeHandle requires at least JDK 7");
471     }
472 }
473